#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>


#include <libintl.h>   
/* i18n: gettext 11-3 */
/* 统一用 GETTEXT_PACKAGE 作为域名（你在 configure.ac 里就是 "audit-gm"） */
#ifndef GETTEXT_PACKAGE
#define GETTEXT_PACKAGE "audit-gm"
#endif

#ifndef LOCALEDIR
#define LOCALEDIR "/usr/local/share/locale"  /* 和 configure 的前缀一致 */
#endif

/* 如果你还没定义 _ / N_ 宏，补上 */
#ifndef _
#define _(s) gettext(s)
#endif
#ifndef N_
#define N_(s) (s)
#endif

#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/err.h>

#include "alarm.c"
#include "../../lib/gm_crypto.h"

#define SM2_ID "1234567812345678"
static const char HEX_CHARS[] = "0123456789abcdef";


static void i18n_init(void) {
    bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    textdomain(GETTEXT_PACKAGE);
}

/* ===================== 工具函数 ===================== */
static void diee(const char *m)
{
    fprintf(stderr, "%s\n", m);
    exit(1);
}
static int file_exists(const char *p)
{
    struct stat st;
    return (p && *p && stat(p, &st) == 0 && S_ISREG(st.st_mode));
}
static int is_directory(const char *p)
{
    struct stat st;
    return (p && *p && stat(p, &st) == 0 && S_ISDIR(st.st_mode));
}
static void hexdump_buf(const unsigned char *buf, size_t len, char *out, size_t outlen)
{
    if (outlen < len*2 + 1) return;
    for (size_t i = 0; i < len; i++) {
        out[i*2]     = HEX_CHARS[(buf[i] >> 4) & 0xF];
        out[i*2 + 1] = HEX_CHARS[buf[i] & 0xF];
    }
    out[len*2] = 0;
}
static unsigned char *b64_decode(const char *b64, size_t *out_len)
{
    BIO *b = BIO_new(BIO_f_base64());
    BIO *m = BIO_new_mem_buf(b64, -1);
    if (!b || !m) { if (b) BIO_free(b); if (m) BIO_free(m); return NULL; }
#ifdef BIO_FLAGS_BASE64_NO_NL
    BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
#endif
    BIO_push(b, m);
    size_t cap = strlen(b64) * 3 / 4 + 16;
    unsigned char *buf = (unsigned char*)malloc(cap);
    int n = BIO_read(b, buf, (int)cap);
    BIO_free_all(b);
    if (n <= 0) { free(buf); return NULL; }
    *out_len = (size_t)n;
    return buf;
}
static size_t normalize_line(char *buf, size_t len)
{
    while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
    return len;
}
static int sm3_file_hex(const char *path, char out_hex[65])
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) { fclose(f); return -1; }
    if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    unsigned char buf[4096];
    size_t n;
    while ((n = fread(buf,1,sizeof(buf),f)) > 0) {
        if (EVP_DigestUpdate(ctx, buf, n) != 1) { EVP_MD_CTX_free(ctx); fclose(f); return -1; }
    }
    fclose(f);
    unsigned char md[32]; unsigned int mdlen = 0;
    if (EVP_DigestFinal_ex(ctx, md, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); return -1; }
    EVP_MD_CTX_free(ctx);
    hexdump_buf(md, 32, out_hex, 65);
    return 0;
}
static int count_lines(const char *path, unsigned long long *out_lines)
{
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    char *line = NULL; size_t cap = 0; ssize_t n;
    unsigned long long ln = 0;
    while ((n = getline(&line, &cap, f)) != -1) ln++;
    free(line);
    fclose(f);
    *out_lines = ln;
    return 0;
}
static int sm3_chain_file_limit(const char *path, unsigned long long limit_lines, unsigned char out32[32], unsigned long long *out_scanned)
{
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) return -1;
    unsigned char H[32] = {0};
    unsigned long long scanned = 0;

    FILE *fp = fopen(path, "rb");
    if (!fp) return -1;
    char *line = NULL; size_t cap = 0; ssize_t n;
    while (scanned < limit_lines && (n = getline(&line, &cap, fp)) != -1) {
        size_t normlen = normalize_line(line, (size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        if (!ctx) { fclose(fp); free(line); return -1; }
        if (EVP_DigestInit_ex(ctx, sm3, NULL) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        if (EVP_DigestUpdate(ctx, H, sizeof(H)) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        if (EVP_DigestUpdate(ctx, line, normlen) != 1) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        unsigned int mdlen = 0;
        if (EVP_DigestFinal_ex(ctx, H, &mdlen) != 1 || mdlen != 32) { EVP_MD_CTX_free(ctx); fclose(fp); free(line); return -1; }
        EVP_MD_CTX_free(ctx);
        scanned++;
    }
    free(line);
    fclose(fp);
    memcpy(out32, H, 32);
    if (out_scanned) *out_scanned = scanned;
    return 0;
}

/* ===================== seal 解析 ===================== */
struct seal {
    char *version, *hash, *sign, *sm2_id, *path, *records, *chain, *key_fpr, *index_sm3, *cert_b64, *sig_b64;
};
static void trimnl(char *s) { size_t n = strlen(s); while (n && (s[n-1]=='\n' || s[n-1]=='\r')) s[--n] = 0; }
static char *trimws(char *s) {
    while (*s == ' ' || *s == '\t') s++;
    size_t n = strlen(s);
    while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\r' || s[n-1] == '\n')) s[--n]=0;
    return s;
}
static int parse_seal(const char *seal_path, struct seal *S)
{
    memset(S, 0, sizeof(*S));
    FILE *f = fopen(seal_path, "r");
    if (!f) return -1;
    char *line = NULL; size_t cap = 0; ssize_t n;
    while ((n = getline(&line, &cap, f)) != -1) {
        if (line[0] == '#' || line[0] == 0) continue;
        trimnl(line);
        char *eq = strchr(line, '=');
        if (!eq) continue;
        *eq = 0;
        char *k = trimws(line);
        char *v = trimws(eq+1);
        if (strcmp(k,"version")==0) S->version = strdup(v);
        else if (strcmp(k,"hash")==0) S->hash = strdup(v);
        else if (strcmp(k,"sign")==0) S->sign = strdup(v);
        else if (strcmp(k,"sm2_id")==0) S->sm2_id = strdup(v);
        else if (strcmp(k,"path")==0) S->path = strdup(v);
        else if (strcmp(k,"records")==0) S->records = strdup(v);
        else if (strcmp(k,"chain")==0) S->chain = strdup(v);
        else if (strcmp(k,"key_fpr")==0) S->key_fpr = strdup(v);
        else if (strcmp(k,"cert_der_base64")==0) S->cert_b64 = strdup(v);
        else if (strcmp(k,"sig_base64")==0) S->sig_b64 = strdup(v);
        else if (strcmp(k,"index_sm3")==0) S->index_sm3 = strdup(v);
    }
    free(line);
    fclose(f);
    if (S->version && S->hash && S->sign && S->sm2_id && S->path && S->records && S->chain && S->key_fpr && S->cert_b64 && S->sig_b64 && S->index_sm3)
        return 0;
    return -1;
}
static void free_seal(struct seal *S)
{
    free(S->version); free(S->hash); free(S->sign); free(S->sm2_id);
    free(S->path); free(S->records); free(S->chain); free(S->key_fpr);
    free(S->index_sm3); free(S->cert_b64); free(S->sig_b64);
}
static char *build_canonical(const struct seal *S)
{
    char *buf = NULL;
    if (asprintf(&buf,
        "version=%s\n"
        "hash=%s\n"
        "sign=%s\n"
        "sm2_id=%s\n"
        "path=%s\n"
        "records=%s\n"
        "chain=%s\n"
        "key_fpr=%s\n"
        "index_sm3=%s\n",
        S->version, S->hash, S->sign, S->sm2_id, S->path, S->records, S->chain, S->key_fpr, S->index_sm3) < 0) return NULL;
    return buf;
}
static int read_idx_tail(const char *idx_path, unsigned long long *out_i, char chash_hex[65])
{
    FILE *f = fopen(idx_path, "r");
    if (!f) return -1;
    char buf[1024];
    unsigned long long last_i = 0;
    char last_chash[65] = {0};

    while (fgets(buf, sizeof(buf), f)) {
        if (buf[0] == '#') continue;
        unsigned long long i=0, off=0;
        char lhex[128]={0}, chex[128]={0};
        if (sscanf(buf, "i=%llu off=%llu lhash=%64s chash=%64s", &i, &off, lhex, chex) == 4) {
            last_i = i;
            strncpy(last_chash, chex, sizeof(last_chash)-1);
        }
    }
    fclose(f);
    if (last_i == 0) return -1;
    if (out_i) *out_i = last_i;
    strncpy(chash_hex, last_chash, 65);
    return 0;
}

/* ===================== 旧的逐行比较（保留给 idx 校验分支） ===================== */
typedef struct {
    unsigned long long *lines;
    size_t count, cap;
} BadList;
static void badlist_init(BadList *b) { b->lines = NULL; b->count = b->cap = 0; }
static void badlist_free(BadList *b) { free(b->lines); b->lines = NULL; b->count = b->cap = 0; }
static void badlist_add(BadList *b, unsigned long long ln) {
    if (!b) return;
    if (b->count == b->cap) {
        size_t nc = b->cap ? b->cap * 2 : 64;
        unsigned long long *p = (unsigned long long*)realloc(b->lines, nc * sizeof(*p));
        if (!p) return;
        b->lines = p; b->cap = nc;
    }
    b->lines[b->count++] = ln;
}


/* 逐行比对“文件前 max_lines 行”与 idx 的前 max_lines 条 lhash。
   与旧版本不同：不再强制要求 idx 的 i 与 ln 完全相等，只以顺序为准。 */
static unsigned long long compare_with_index_limit(const char *log_path, const char *idx_path,
                                                   BadList *out_bad,
                                                   unsigned long long max_lines,
                                                   unsigned long long *scanned_lines)
{
    if (scanned_lines) *scanned_lines = 0;
    if (out_bad) badlist_init(out_bad);

    FILE *lf = fopen(log_path,"rb");
    FILE *ix = fopen(idx_path,"r");
    if (!lf || !ix) { if (lf) fclose(lf); if (ix) fclose(ix); return (unsigned long long)-1; }

    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) { fclose(lf); fclose(ix); return (unsigned long long)-1; }

    char *line = NULL; size_t cap = 0; ssize_t n;
    char ibuf[1024];
    unsigned long long ln = 0, bad = 0;

    /* 跳到 idx 第一条数据行 */
    long back_pos = -1;
    while (fgets(ibuf, sizeof(ibuf), ix)) {
        if (ibuf[0] == '#') continue;
        if (strncmp(ibuf, "i=", 2) == 0) { back_pos = (long)ftell(ix) - (long)strlen(ibuf); break; }
    }
    if (back_pos >= 0) fseek(ix, back_pos, SEEK_SET);
    else rewind(ix);

    while ((n = getline(&line, &cap, lf)) != -1) {
        ln++;
        if (max_lines != 0 && ln > max_lines) break;

        /* 取一条 idx 记录（忽略 i/off，只拿 lhash） */
        if (!fgets(ibuf, sizeof(ibuf), ix)) {
            bad++; if (out_bad) badlist_add(out_bad, ln);
            continue;
        }
        unsigned long long i_dummy=0, off_dummy=0;
        char lhex_idx[128]={0}, chex_dummy[128]={0};
        if (sscanf(ibuf, "i=%llu off=%llu lhash=%127s chash=%127s",
                   &i_dummy, &off_dummy, lhex_idx, chex_dummy) != 4) {
            bad++; if (out_bad) badlist_add(out_bad, ln);
            continue;
        }

        /* 计算当前行的 lhash 并比对 */
        size_t normlen = normalize_line(line, (size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        unsigned char L[32]; unsigned int lm=0;
        int ok = ctx &&
                 EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
                 EVP_DigestUpdate(ctx, line, normlen)==1 &&
                 EVP_DigestFinal_ex(ctx, L, &lm)==1 && lm==32;
        EVP_MD_CTX_free(ctx);
        if (!ok) { free(line); fclose(lf); fclose(ix); return (unsigned long long)-1; }

        char Lhex[65];
        hexdump_buf(L, 32, Lhex, sizeof(Lhex));
        if (strcmp(Lhex, lhex_idx) != 0) {
            bad++; if (out_bad) badlist_add(out_bad, ln);
        }
    }

    if (scanned_lines) *scanned_lines = (max_lines == 0) ? ln : (ln > max_lines ? max_lines : ln);
    free(line); fclose(lf); fclose(ix);
    return bad;
}



/* ===================== 新增：重同步差异检测 ===================== */
typedef struct { char (*hex)[65]; size_t count, cap; } HexVec;
static void hv_free(HexVec *v){ if(v){ free(v->hex); v->hex=NULL; v->count=v->cap=0; } }
static int hv_push(HexVec *v, const char hex64[65]){
    if (v->count == v->cap) {
        size_t nc = v->cap ? v->cap*2 : 256;
        void *p = realloc(v->hex, nc * sizeof(*v->hex));
        if (!p) return -1;
        v->hex = (char (*)[65])p; v->cap = nc;
    }
    strncpy(v->hex[v->count], hex64, 65);
    v->count++;
    return 0;
}

/* 从 idx 顺序读取前 N 条 lhash，忽略 i/off 字段的值 */
static int load_expected_from_idx(const char *idx_path, unsigned long long N, HexVec *E){
    if (!E || N == 0) { memset(E, 0, sizeof(*E)); return 0; }
    FILE *f = fopen(idx_path, "r");
    if (!f) return -1;

    char buf[1024];
    unsigned long long taken = 0;

    while (fgets(buf, sizeof(buf), f)) {
        if (buf[0] == '#') continue;
        unsigned long long i_dummy=0, off_dummy=0;
        char lhex[128]={0}, chex[128]={0};
        if (sscanf(buf,"i=%llu off=%llu lhash=%127s chash=%127s",&i_dummy,&off_dummy,lhex,chex)==4) {
            if (hv_push(E, lhex) != 0) { fclose(f); return -1; }
            taken++;
            if (taken >= N) break;
        }
    }
    fclose(f);
    return 0;
}


static int compute_file_lhashes(const char *log_path, unsigned long long limit, HexVec *A, unsigned long long *out_file_lines){
    FILE *lf = fopen(log_path, "rb");
    if (!lf) return -1;
    const EVP_MD *sm3 = EVP_get_digestbyname("sm3");
    if (!sm3) { fclose(lf); return -1; }
    char *line=NULL; size_t cap=0; ssize_t n;
    unsigned long long i=0, total=0;
    while ((n = getline(&line, &cap, lf)) != -1) {
        total++;
        if (limit && i >= limit) continue; /* 仍统计总行数 */
        size_t norm = normalize_line(line, (size_t)n);
        EVP_MD_CTX *ctx = EVP_MD_CTX_new(); unsigned char md[32]; unsigned int mdlen=0;
        int ok = ctx && EVP_DigestInit_ex(ctx, sm3, NULL)==1 &&
                 EVP_DigestUpdate(ctx, line, norm)==1 &&
                 EVP_DigestFinal_ex(ctx, md, &mdlen)==1 && mdlen==32;
        EVP_MD_CTX_free(ctx);
        if (!ok) { free(line); fclose(lf); return -1; }
        char hex[65]; hexdump_buf(md, 32, hex, sizeof(hex));
        if (i < limit) { if (hv_push(A, hex) != 0) { free(line); fclose(lf); return -1; } i++; }
    }
    free(line); fclose(lf);
    if (out_file_lines) *out_file_lines = total;
    return 0;
}

typedef struct {
    unsigned long long *modified; size_t mod_n, mod_cap;  /* 被封印的行中“改写/替换”的行号（按 seal 行号） */
    unsigned long long *deleted;  size_t del_n, del_cap;  /* 被封印的行中“缺失/删除”的行号（按 seal 行号） */
    unsigned long long *extra_file; size_t ext_n, ext_cap;/* 文件中插入的额外行（用文件 1-based 行号，仅提示） */
    unsigned long long *modified_file;size_t modf_n, modf_cap; /*按 当前文件行号*/
} DiffOut;
static void diffout_init(DiffOut *d){ memset(d,0,sizeof(*d)); }
static void diffout_free(DiffOut *d){ free(d->modified); free(d->deleted); free(d->extra_file); free(d->modified_file); }
static int u64_push(unsigned long long **arr, size_t *n, size_t *cap, unsigned long long v){
    if (*n == *cap) {
        size_t nc = *cap ? *cap*2 : 64;
        void *p = realloc(*arr, nc * sizeof(unsigned long long));
        if (!p) return -1;
        *arr = (unsigned long long*)p; *cap = nc;
    }
    (*arr)[(*n)++] = v; return 0;
}

#define LOOKAHEAD_FILE  128    /* 文件侧前瞻窗口 */
#define LOOKAHEAD_IDX   128    /* 期望侧前瞻窗口 */
#define EXTRA_SCAN      512   /* 在 N 基础上多读的文件行数用于重同步 */


/* 用 Myers O(ND) 差分做“重同步差异检测”，并把相邻的 DEL+INS/INS+DEL 折算成 MODIFIED。
   - E：期望（被封印的行）    -> 删除(delete) 代表“封印行缺失”，记到 out->deleted
   - A：实际（当前文件的行）  -> 删除(delete) 代表“文件多了一行”，记到 out->extra_file
   - 把一对“删除+插入”折算为改写，分别记到 out->modified(按封印行号) 与 out->modified_file(按文件行号)
*/
/* —— 直接替换 audit-verify 中的 resync_diff —— */
static int resync_diff(const HexVec *E, const HexVec *A, DiffOut *out)
{
    if (!E || !A || !out) return -1;

    const size_t m = A->count; /* 文件侧（actual） */
    const size_t n = E->count; /* 封印侧（expected） */

    if (m == 0 && n == 0) return 0;
    if (m == 0) { for (size_t j=0;j<n;j++) if (u64_push(&out->deleted,&out->del_n,&out->del_cap,(unsigned long long)(j+1))!=0) return -1; return 0; }
    if (n == 0) { for (size_t i=0;i<m;i++) if (u64_push(&out->extra_file,&out->ext_n,&out->ext_cap,(unsigned long long)(i+1))!=0) return -1; return 0; }

    typedef enum { OP_MATCH=0, OP_INS=1, OP_DEL=2 } Op;

    size_t maxd = m + n;
    size_t vvlen = 2*maxd + 3;
    int **trace = (int**)calloc(maxd + 1, sizeof(int*));
    if (!trace) return -1;
    int *V = (int*)calloc(vvlen, sizeof(int));
    if (!V) { free(trace); return -1; }
    int offset = (int)(maxd + 1);

    int found = 0; size_t found_d = 0;

    for (size_t d = 0; d <= maxd; d++) {
        int *Vcopy = (int*)malloc(vvlen * sizeof(int));
        if (!Vcopy) { for (size_t t=0;t<d;t++) free(trace[t]); free(trace); free(V); return -1; }
        memcpy(Vcopy, V, vvlen * sizeof(int));
        trace[d] = Vcopy;

        for (int k = -(int)d; k <= (int)d; k += 2) {
            int x, y;
            if (k == -(int)d || (k != (int)d && V[offset + k - 1] < V[offset + k + 1])) {
                x = V[offset + k + 1];      /* 来自向下 -> INS（缺封印行） */
            } else {
                x = V[offset + k - 1] + 1;  /* 来自向右 -> DEL（文件多行） */
            }
            y = x - k;

            while ((size_t)x < m && (size_t)y < n && strcmp(A->hex[x], E->hex[y]) == 0) { x++; y++; }
            V[offset + k] = x;

            if ((size_t)x >= m && (size_t)y >= n) { found = 1; found_d = d; break; }
        }
        if (found) break;
    }

    if (!found) { for (size_t d=0; d<=found_d; d++) free(trace[d]); free(trace); free(V); return -1; }

    /* 回溯得到操作序列（逆 -> 正） */
    typedef unsigned char U8;
    U8 *ops = NULL; size_t ops_n = 0, ops_cap = 0;
    #define OP_PUSH(o) do{ if(ops_n==ops_cap){ size_t nc=ops_cap?ops_cap*2:256; U8*pp=(U8*)realloc(ops,nc); if(!pp){rc=-1; goto cleanup;} ops=pp; ops_cap=nc; } ops[ops_n++]=(U8)(o);}while(0)

    int rc = 0;
    int x = (int)m, y = (int)n;
    for (int d = (int)found_d; d >= 0; d--) {
        int *Vprev = trace[d];
        int k = x - y;
        int prev_k = (k == -d || (k != d && Vprev[offset + k - 1] < Vprev[offset + k + 1])) ? (k + 1) : (k - 1);
        int prev_x = Vprev[offset + prev_k];
        int prev_y = prev_x - prev_k;

        while (x > prev_x && y > prev_y) { OP_PUSH(OP_MATCH); x--; y--; }

        if (d > 0) {
            if (x == prev_x) { OP_PUSH(OP_INS); y--; }
            else             { OP_PUSH(OP_DEL); x--; }
        }
    }
    for (size_t l=0, r=ops_n?ops_n-1:0; l<r; l++, r--) { U8 t=ops[l]; ops[l]=ops[r]; ops[r]=t; }

    /* —— 关键：以“非 MATCH 连续块”为单位做批量配对 —— */
    {
        size_t i = 0, j = 0;       /* 当前文件/封印游标（0-based） */
        size_t p = 0;
        while (p < ops_n) {
            if (ops[p] == OP_MATCH) { i++; j++; p++; continue; }

            /* 收集一段连续 [p..q) 的 INS/DEL */
            size_t q = p;
            /* 暂存本段中每个 DEL 的“文件行号 i0”，每个 INS 的“封印行号 j0” */
            size_t del_cap=0, ins_cap=0, del_n=0, ins_n=0;
            size_t *del_i = NULL, *ins_j = NULL;

            while (q < ops_n && ops[q] != OP_MATCH) {
                if (ops[q] == OP_DEL) {
                    if (del_n == del_cap) { size_t nc = del_cap?del_cap*2:8; size_t *pp=(size_t*)realloc(del_i, nc*sizeof(size_t)); if(!pp){ rc=-1; goto cleanup; } del_i=pp; del_cap=nc; }
                    del_i[del_n++] = i;  /* 记录当前文件行号（0-based） */
                    i++;                 /* DEL 消耗文件侧一行 */
                } else { /* OP_INS */
                    if (ins_n == ins_cap) { size_t nc = ins_cap?ins_cap*2:8; size_t *pp=(size_t*)realloc(ins_j, nc*sizeof(size_t)); if(!pp){ free(del_i); rc=-1; goto cleanup; } ins_j=pp; ins_cap=nc; }
                    ins_j[ins_n++] = j;  /* 记录当前封印行号（0-based） */
                    j++;                 /* INS 消耗封印侧一行 */
                }
                q++;
            }

            /* 先把能一一对应的部分折算为“改写” */
            size_t kpair = del_n < ins_n ? del_n : ins_n;
            for (size_t t = 0; t < kpair; t++) {
                if (u64_push(&out->modified, &out->mod_n, &out->mod_cap, (unsigned long long)(ins_j[t] + 1)) != 0) { free(del_i); free(ins_j); rc=-1; goto cleanup; }
                if (u64_push(&out->modified_file, &out->modf_n, &out->modf_cap, (unsigned long long)(del_i[t] + 1)) != 0) { free(del_i); free(ins_j); rc=-1; goto cleanup; }
            }
            /* 多出来的 DEL -> 文件多余（extra_file） */
            for (size_t t = kpair; t < del_n; t++) {
                if (u64_push(&out->extra_file, &out->ext_n, &out->ext_cap, (unsigned long long)(del_i[t] + 1)) != 0) { free(del_i); free(ins_j); rc=-1; goto cleanup; }
            }
            /* 多出来的 INS -> 封印缺失（deleted） */
            for (size_t t = kpair; t < ins_n; t++) {
                if (u64_push(&out->deleted, &out->del_n, &out->del_cap, (unsigned long long)(ins_j[t] + 1)) != 0) { free(del_i); free(ins_j); rc=-1; goto cleanup; }
            }
            free(del_i); free(ins_j);
            p = q;
        }
    }

cleanup:
    if (trace) { for (size_t d=0; d<=found_d; d++) free(trace[d]); free(trace); }
    free(V);
    free(ops);
    return rc;
}



/* ===================== 寻找匹配日志（按链） ===================== */
static int find_matching_log_by_chain(const char *seal_path, const struct seal *S, char out_log_path[PATH_MAX])
{
    (void)seal_path;
    if (!S || !S->chain || !S->records) return -1;
    unsigned long long N = strtoull(S->records, NULL, 10);
    if (N == 0) return -1;

    char dir[PATH_MAX];
    snprintf(dir, sizeof(dir), "%s", S->path && strchr(S->path, '/') ? S->path : "/var/log/audit/audit.log");
    char *slash = strrchr(dir, '/');
    if (!slash) return -1;
    *slash = 0;
    if (!*dir) strcpy(dir, "/");

    DIR *d = opendir(dir);
    if (!d) return -1;
    struct dirent *de;
    int ok = -1;

    while ((de = readdir(d))) {
        if (strncmp(de->d_name, "audit.log", 9) != 0) continue;
        char cand[PATH_MAX];
        snprintf(cand, sizeof(cand), "%s/%s", dir, de->d_name);
        if (!file_exists(cand)) continue;

        unsigned char cbin[32]; char chex[65];
        if (sm3_chain_file_limit(cand, N, cbin, NULL) != 0) continue;
        hexdump_buf(cbin, 32, chex, sizeof(chex));
        if (strcmp(chex, S->chain) == 0) {
            strncpy(out_log_path, cand, PATH_MAX-1);
            out_log_path[PATH_MAX-1] = 0;
            ok = 0;
            break;
        }
    }
    closedir(d);
    return ok;
}

/* 极简告警输出：只报你关心的行，别的都不说 */
static void emit_minimal_tamper_report(const DiffOut *D)
{
    int printed = 0;

    /* 改写/替换：按“当前文件行号”报 */
    if (D->modf_n) {
        fprintf(stderr, _("[ALARM] Tampered file lines: "));
        for (size_t i = 0; i < D->modf_n; i++) {
            fprintf(stderr, "%llu%s", D->modified_file[i],
                    (i + 1 < D->modf_n) ? "," : "");
        }
        fputc('\n', stderr);
        printed = 1;
    }

    /* 删除：按“封印行号”报（因为文件里已经没这几行了） */
    if (D->del_n) {
        fprintf(stderr, _("[ALARM] Deleted sealed lines: "));
        for (size_t i = 0; i < D->del_n; i++) {
            fprintf(stderr, "%llu%s", D->deleted[i],
                    (i + 1 < D->del_n) ? "," : "");
        }
        fputc('\n', stderr);
        printed = 1;
    }

    /* 插入：按“当前文件行号”报 */
    if (D->ext_n) {
        fprintf(stderr, _("[ALARM] Inserted file lines: "));
        for (size_t i = 0; i < D->ext_n; i++) {
            fprintf(stderr, "%llu%s", D->extra_file[i],
                    (i + 1 < D->ext_n) ? "," : "");
        }
        fputc('\n', stderr);
        printed = 1;
    }

    /* 触发中心化告警（拼一条短消息即可） */
    {
        char msg[1024]; size_t pos = 0;
        if (D->modf_n) {
            // pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered(file): ");
            pos += snprintf(msg + pos, sizeof(msg) - pos, "%s", _("Tampered(file): ")); //11-3
            for (size_t i = 0; i < D->modf_n && pos < sizeof(msg) - 8; i++)
                pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s",
                                D->modified_file[i], (i + 1 < D->modf_n) ? "," : "");
        }
        if (D->del_n && pos < sizeof(msg) - 16) {
            if (pos) pos += snprintf(msg + pos, sizeof(msg) - pos, " | ");
            // pos += snprintf(msg + pos, sizeof(msg) - pos, "Deleted(sealed): ");
            pos += snprintf(msg + pos, sizeof(msg) - pos, "%s", _("Deleted(sealed): ")); //11-3
            for (size_t i = 0; i < D->del_n && pos < sizeof(msg) - 8; i++)
                pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s",
                                D->deleted[i], (i + 1 < D->del_n) ? "," : "");
        }
        if (D->ext_n && pos < sizeof(msg) - 16) {
            if (pos) pos += snprintf(msg + pos, sizeof(msg) - pos, " | ");
            // pos += snprintf(msg + pos, sizeof(msg) - pos, "Inserted(file): ");
            pos += snprintf(msg + pos, sizeof(msg) - pos, "%s", _("Inserted(file): ")); //11-3
            for (size_t i = 0; i < D->ext_n && pos < sizeof(msg) - 8; i++)
                pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s",
                                D->extra_file[i], (i + 1 < D->ext_n) ? "," : "");
        }
        // if (pos == 0) snprintf(msg, sizeof(msg), "Chain mismatch");
        if (pos == 0) snprintf(msg, sizeof(msg), "%s", _("Chain mismatch"));  //11-3
        trigger_alarm(msg);
    }

    if (!printed) {
        /* 理论上不会到这；保底一句 */
        //fprintf(stderr, "[ALARM] Chain mismatch.\n");
        fprintf(stderr, "%s\n", _("[ALARM] Chain mismatch.")); //11-3
    }
}

// ---------2025-10.22----------
/* 过滤“封印范围之外”的插入行：仅保留 <= seal_records 的插入；
 * 其余被移除，并返回被忽略的范围与数量（用于可选提示）。
 */
static void filter_tail_insertions(DiffOut *D,
                                   unsigned long long seal_records,
                                   unsigned long long *out_from,
                                   unsigned long long *out_to,
                                   size_t *out_n)
{
    if (!D || D->ext_n == 0) {
        if (out_n) *out_n = 0;
        if (out_from) *out_from = 0;
        if (out_to) *out_to = 0;
        return;
    }

    size_t w = 0;                 /* 写回游标（压缩保留 <= seal_records 的条目） */
    unsigned long long minL = 0, maxL = 0;
    size_t dropped = 0;

    for (size_t r = 0; r < D->ext_n; r++) {
        unsigned long long line = D->extra_file[r];
        if (line > seal_records) {
            if (!dropped) { minL = maxL = line; }
            if (line < minL) minL = line;
            if (line > maxL) maxL = line;
            dropped++;
            continue;  /* 丢弃封印之外的“插入” */
        }
        if (w != r) D->extra_file[w] = D->extra_file[r];
        w++;
    }
    D->ext_n = w;

    if (out_from) *out_from = dropped ? minL : 0;
    if (out_to)   *out_to   = dropped ? maxL : 0;
    if (out_n)    *out_n    = dropped;
}
// ---------2025-10.22----------

//验证逻辑
static int verify_one(const char *seal_path)
{
   // fprintf(stderr, "== Verifying: %s ==\n", seal_path);
    fprintf(stderr, _("== Verifying: %s ==\n"), seal_path);  //11-3

    unsigned long long boundary_extra_from = 0, boundary_extra_to = 0;
    int boundary_has_extra = 0;

    /* 1) 解析 + 证书/签名校验（保持原有） */
    struct seal S;
    if (parse_seal(seal_path, &S) != 0) {
        // trigger_alarm("Seal file is not parseable!");
        // fprintf(stderr, "[FAIL] parse seal failed: %s\n\n", seal_path);

        //11-3
        trigger_alarm(_("Seal file is not parseable!"));
        fprintf(stderr, _("[FAIL] parse seal failed: %s\n\n"), seal_path);


        return 2;
    }
    if (strcmp(S.hash, "sm3") != 0 || strcmp(S.sign, "sm2") != 0) {
        // trigger_alarm("Seal algorithm mismatch (expect SM2/SM3)");
        // fprintf(stderr, "[ERROR] seal 非 sm2/sm3\n\n");

        //11-3
        trigger_alarm(_("Seal algorithm mismatch (expect SM2/SM3)"));
        fprintf(stderr, _("[ERROR] Seal is not SM2/SM3.\n\n"));

        free_seal(&S);
        return 2;
    }
    if (strcmp(S.sm2_id, SM2_ID) != 0) {
        // trigger_alarm("SM2_ID mismatch!");
        // fprintf(stderr, "[ERROR] SM2_ID 不匹配\n\n");

        //11-3
        trigger_alarm(_("SM2_ID mismatch!"));
        fprintf(stderr, _("[ERROR] SM2_ID mismatch.\n\n"));

        free_seal(&S);
        return 2;
    }

    unsigned long long seal_records = 0;
    if (S.records) seal_records = strtoull(S.records, NULL, 10);

    size_t cert_len = 0;
    unsigned char *cert_der = b64_decode(S.cert_b64, &cert_len);
    if (!cert_der) { fprintf(stderr, "[FAIL] cert b64 decode failed\n\n"); free_seal(&S); return 2; }
    const unsigned char *pp = cert_der;
    X509 *cert = d2i_X509(NULL, &pp, cert_len);
    free(cert_der);
    if (!cert) { fprintf(stderr, "[FAIL] d2i_X509 failed\n\n"); free_seal(&S); return 2; }
    EVP_PKEY *pub = X509_get_pubkey(cert);
    if (!pub) { fprintf(stderr, "[FAIL] X509_get_pubkey failed\n\n"); X509_free(cert); free_seal(&S); return 2; }
    int ptype = EVP_PKEY_id(pub);
    if (ptype != EVP_PKEY_EC && ptype != EVP_PKEY_SM2) {
        // trigger_alarm("Public key is not EC/SM2 key!");
        // fprintf(stderr, "[FAIL] pub is not EC/SM2 (type=%d)\n\n", ptype);

        //11-3
        trigger_alarm(_("Public key is not EC/SM2 key!"));
        fprintf(stderr, _("[FAIL] Public key is not EC/SM2 (type=%d)\n\n"), ptype);

        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2;
    }
    //fprintf(stderr, "[INFO] 公钥类型: %s\n", (ptype == EVP_PKEY_SM2) ? "SM2" : "EC");
    fprintf(stderr, _("[INFO] Public key type: %s\n"), (ptype == EVP_PKEY_SM2) ? "SM2" : "EC"); //11-3


    /* 指纹对齐（保持原有） */
    unsigned char *der2=NULL; int der2len = i2d_X509(cert, &der2);
    if (der2len <= 0) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); fprintf(stderr, "[FAIL] i2d_X509 failed\n\n"); return 2; }
    unsigned char mdv[EVP_MAX_MD_SIZE]; unsigned int mdvlen = 0;
    EVP_MD_CTX *mctx = EVP_MD_CTX_new();
    if (!mctx || EVP_DigestInit_ex(mctx, EVP_get_digestbyname("sm3"), NULL) != 1 ||
        EVP_DigestUpdate(mctx, der2, der2len) != 1 ||
        EVP_DigestFinal_ex(mctx, mdv, &mdvlen) != 1) {
        if (mctx) EVP_MD_CTX_free(mctx);
        OPENSSL_free(der2); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S);
        fprintf(stderr, "[FAIL] fingerprint digest failed\n\n");
        return 2;
    }
    EVP_MD_CTX_free(mctx);
    OPENSSL_free(der2);
    char fpr_hex[65]; hexdump_buf(mdv, mdvlen, fpr_hex, sizeof(fpr_hex));
    if (strcmp(fpr_hex, S.key_fpr) != 0) {
        // trigger_alarm("Certificate fingerprint mismatch detected!");
        // fprintf(stderr, "[FAIL] key_fpr mismatch\n\n");

        //11-3
        trigger_alarm(_("Certificate fingerprint mismatch detected!"));
        fprintf(stderr, _("[FAIL] key_fpr mismatch\n\n"));

        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2;
    }

    /* 验签（保持原有） */
    char *canon = build_canonical(&S);
    if (!canon) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); fprintf(stderr, "[FAIL] asprintf canon\n\n"); return 2; }
    size_t siglen = 0; unsigned char *sig = b64_decode(S.sig_b64, &siglen);
    if (!sig) { free(canon); EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); fprintf(stderr, "[FAIL] sig b64 decode failed\n\n"); return 2; }
    int vret = gm_sm2_verify(pub, (const unsigned char *)canon, strlen(canon), SM2_ID, sig, siglen);
    free(sig); free(canon);
    if (vret != 1) {
        // trigger_alarm(vret==0 ? "Signature mismatch detected!" : "Signature verification error!");
        // fprintf(stderr, (vret==0) ? "[FAIL] signature invalid\n\n" : "[FAIL] verify error\n\n");

        //11-3
        trigger_alarm(vret==0 ? _("Signature mismatch detected!") : _("Signature verification error!"));
        fprintf(stderr, "%s", (vret==0) ? _("[FAIL] signature invalid\n\n") : _("[FAIL] verify error\n\n"));

        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2;
    }

    /* 2) 找日志并计行（保持原有） */
    char log_path[PATH_MAX]; snprintf(log_path, sizeof(log_path), "%s", S.path);
    if (!file_exists(log_path)) {
        char matched[PATH_MAX] = {0};
        if (find_matching_log_by_chain(seal_path, &S, matched) == 0) {
            // fprintf(stderr, "[INFO] seal.path 不存在，已定位对应日志：%s\n", matched);

            //11-3
            fprintf(stderr, _("[INFO] Seal path not found; matched audit log: %s\n"), matched);

            strncpy(log_path, matched, sizeof(log_path)-1);
        } else {
            // trigger_alarm("Referenced audit log not found for this seal!");
            // fprintf(stderr, "[FAIL] referenced audit log not found: %s\n\n", S.path);

            //11-3
            trigger_alarm(_("Referenced audit log not found for this seal!"));
            fprintf(stderr, _("[FAIL] referenced audit log not found: %s\n\n"), S.path);

            EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2;
        }
    }
    unsigned long long log_lines = 0;
    if (count_lines(log_path, &log_lines) != 0) {
        trigger_alarm("Cannot open audit log");
        //fprintf(stderr, "[ERROR] cannot count lines of audit log: %s\n\n", log_path);

        //11-3
        fprintf(stderr, _("[ERROR] cannot count lines of audit log: %s\n\n"), log_path);

        EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); return 2;
    }
    //fprintf(stderr, "[DEBUG] %s has %llu lines; seal.records=%llu\n",
    //11-3
    fprintf(stderr, _("[DEBUG] %s has %llu lines; seal.records=%llu\n"),  
            log_path, log_lines, (unsigned long long)seal_records);

    /* 2.5) —— tampered 快照“边界一致性”强校验 —— */
    int is_snapshot =
        (strstr(seal_path, ".tampered-") != NULL) ||
        (S.path && strstr(S.path, ".tampered-") != NULL) ||
        (strstr(log_path, ".tampered-") != NULL);

    /* FIX: 引入 boundary_bad 标志，遇到边界不一致“报警但不 return”，后续仍跑链/idx 差异定位 */
    int boundary_bad = 0;

    if (is_snapshot) {
        if (log_lines > seal_records) {
            boundary_bad = 1;
            boundary_has_extra = 1;
            boundary_extra_from = seal_records + 1;
            boundary_extra_to   = log_lines;
            /* 这里不打印；等链校验后再决定是否打印 */
        } else if (log_lines < seal_records) {
            boundary_bad = 1;
            /* 缺行这个分支你原本也没输出具体行号，这里保持现状不打印 */
        }
    }


    char *idx_path = NULL;
    if (asprintf(&idx_path, "%s.idx", log_path) < 0) idx_path = NULL;
    if (!idx_path) { EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); fprintf(stderr, "[FAIL] OOM for idx path\n\n"); return 2; }

    /* 3) 链式校验（前 N 行） */
    int chain_bad = 0;
    if (seal_records > 0) {
        unsigned char chain_now[32];
        if (sm3_chain_file_limit(log_path, seal_records, chain_now, NULL) != 0) {
            // fprintf(stderr, "[ERR] failed to compute chain over first N lines\n\n");
            fprintf(stderr, _("[ERR] failed to compute chain over first N lines\n\n")); //11-3
            EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path); return 2;
        }
        char chain_now_hex[65]; hexdump_buf(chain_now, 32, chain_now_hex, sizeof(chain_now_hex));
        if (strcmp(chain_now_hex, S.chain) != 0) {
            chain_bad = 1;
            // trigger_alarm("Chain mismatch detected: audit log may be tampered!");
            // fprintf(stderr, "[FAIL] chain mismatch (文件被改动?)\n");

            //11-3
            trigger_alarm(_("Chain mismatch detected: audit log may be tampered!"));
            fprintf(stderr, _("[FAIL] Chain mismatch (audit.log may be modified?)\n"));

            if (access(idx_path, R_OK) == 0) {
                HexVec E = {0}, A = {0};
                DiffOut D; diffout_init(&D);

                if (load_expected_from_idx(idx_path, seal_records, &E) == 0) {
                    unsigned long long N_eff = E.count;
                    if (N_eff == 0) {
                        //fprintf(stderr, "[WARN] idx empty for %s; cannot locate exact changed lines.\n", log_path);
                        fprintf(stderr, _("[WARN] idx empty for %s; cannot locate exact changed lines.\n"), log_path); //11-3
                    } else {
                        if (N_eff < seal_records) {
                            //fprintf(stderr, "[WARN] idx entries (%llu) < seal.records (%llu); narrowing diff to %llu line(s).\n",
                            //11-3
                            fprintf(stderr, _("[WARN] idx entries (%llu) < seal.records (%llu); narrowing diff to %llu line(s).\n"),
                                    (unsigned long long)N_eff, (unsigned long long)seal_records, (unsigned long long)N_eff);
                        }
                        unsigned long long dummy = 0;
                        if (compute_file_lhashes(log_path, N_eff + EXTRA_SCAN, &A, &dummy) == 0) {
                            if (resync_diff(&E, &A, &D) == 0) {
                                if (D.modf_n) {
                                    // fprintf(stderr, "[ALARM] Tampered file lines: ");
                                    //11-3
                                    fprintf(stderr, _("[ALARM] Tampered file lines: "));
                                    for (size_t i = 0; i < D.modf_n; i++)
                                        fprintf(stderr, "%llu%s", D.modified_file[i], (i + 1 < D.modf_n) ? "," : "");
                                    fputc('\n', stderr);
                                }
                                if (D.del_n) {
                                    // fprintf(stderr, "[ALARM] Deleted sealed lines: ");
                                    //11-3
                                    fprintf(stderr, _("[ALARM] Deleted sealed lines: "));
                                    for (size_t i = 0; i < D.del_n; i++)
                                        fprintf(stderr, "%llu%s", D.deleted[i], (i + 1 < D.del_n) ? "," : "");
                                    fputc('\n', stderr);
                                }
                                if (D.ext_n) {
                                    // fprintf(stderr, "[ALARM] Inserted file lines: ");
                                    //11-3
                                    fprintf(stderr, _("[ALARM] Inserted file lines: "));
                                    for (size_t i = 0; i < D.ext_n; i++)
                                        fprintf(stderr, "%llu%s", D.extra_file[i], (i + 1 < D.ext_n) ? "," : "");
                                    fputc('\n', stderr);
                                }


                                // ---------2025-10.22----------
                                /* —— 仅在“普通 seal 验证”里，忽略封印边界之后的新增尾部 —— */
                                if (!is_snapshot && seal_records > 0) {
                                    unsigned long long ig_from = 0, ig_to = 0;
                                    size_t ig_n = 0;
                                    filter_tail_insertions(&D, seal_records, &ig_from, &ig_to, &ig_n);
                                    /* 可选：给一点提示，避免用户误以为校验器“看不到”新尾部 */
                                    if (ig_n > 0) {
                                        // fprintf(stderr,
                                        //     "[INFO] 忽略未封印的新尾部：%llu..%llu（共%zu行，超出 seal.records=%llu，不参与逐行判定）\n",
                                        //     ig_from, ig_to, ig_n, (unsigned long long)seal_records);
                                        //11-3
                                        fprintf(stderr,
                                            _("[INFO] Ignoring unsealed tail: %llu..%llu (total %zu lines beyond seal.records=%llu; not included in per-line diff)\n"),
                                            ig_from, ig_to, ig_n, (unsigned long long)seal_records);
                                    }
                                }
                                // ---------2025-10.22----------


                                /* 汇总中心化告警 */
                                {
                                    char msg[1024]; size_t pos = 0;
                                    if (D.modf_n) {
                                        // pos += snprintf(msg + pos, sizeof(msg) - pos, "Tampered(file): ");
                                        pos += snprintf(msg + pos, sizeof(msg) - pos, "%s", _("Tampered(file): ")); //11-3
                                        for (size_t i = 0; i < D.modf_n && pos < sizeof(msg) - 8; i++)
                                            pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", D.modified_file[i], (i + 1 < D.modf_n) ? "," : "");
                                    }
                                    if (D.del_n && pos < sizeof(msg) - 16) {
                                        if (pos) pos += snprintf(msg + pos, sizeof(msg) - pos, " | ");
                                        // pos += snprintf(msg + pos, sizeof(msg) - pos, "Deleted(sealed): ");
                                        pos += snprintf(msg + pos, sizeof(msg) - pos, "%s", _("Deleted(sealed): ")); //11-3
                                        for (size_t i = 0; i < D.del_n && pos < sizeof(msg) - 8; i++)
                                            pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", D.deleted[i], (i + 1 < D.del_n) ? "," : "");
                                    }
                                    if (D.ext_n && pos < sizeof(msg) - 16) {
                                        if (pos) pos += snprintf(msg + pos, sizeof(msg) - pos, " | ");
                                        // pos += snprintf(msg + pos, sizeof(msg) - pos, "Inserted(file): "); 
                                        pos += snprintf(msg + pos, sizeof(msg) - pos, "%s", _("Inserted(file): ")); //11-3
                                        for (size_t i = 0; i < D.ext_n && pos < sizeof(msg) - 8; i++)
                                            pos += snprintf(msg + pos, sizeof(msg) - pos, "%llu%s", D.extra_file[i], (i + 1 < D.ext_n) ? "," : "");
                                    }
                                    // if (pos == 0) snprintf(msg, sizeof(msg), "Chain mismatch");
                                    if (pos == 0) snprintf(msg, sizeof(msg), "%s", _("Chain mismatch")); //11-3
                                    trigger_alarm(msg);
                                }
                            }
                        }
                    }
                }
                hv_free(&E); hv_free(&A); diffout_free(&D);
            } else {
                // fprintf(stderr, "[WARN] idx missing for %s; cannot locate exact changed lines.\n", log_path);
                fprintf(stderr, _("[WARN] idx missing for %s; cannot locate exact changed lines.\n"), log_path); //11-3
            }

            fprintf(stderr, "\n");
        }
    }

    /* 4) （链未失配时）index_sm3 绑定与尾项一致性 */

    /* 若是快照且没有发生链失配，则补打印边界阶段缓存的 Inserted 行 */
    if (boundary_has_extra && !chain_bad) {
        if (boundary_extra_from == boundary_extra_to) {
            // fprintf(stderr, "[ALARM] Inserted file lines: %llu\n\n", boundary_extra_from);
            fprintf(stderr, _("[ALARM] Inserted file lines: %llu\n\n"), boundary_extra_from); //11-3
        } else {
            //fprintf(stderr, "[ALARM] Inserted file lines: ");
            fprintf(stderr, _("[ALARM] Inserted file lines: ")); //11-3
            for (unsigned long long ln = boundary_extra_from; ln <= boundary_extra_to; ++ln) {
                fprintf(stderr, "%llu%s", ln, (ln < boundary_extra_to) ? "," : "");
            }
            fputc('\n', stderr);
        }
        /* 这里不要再额外 fprintf("\n")，避免多出空行 */
    }



    int idx_ok = 0, idx_tail_ok = 0;
    /* FIX: 如果之前触发了 boundary_bad，也要跳过 idx 的“正常一致性”分支，避免边界坏却打印 OK 的错觉 */
    if (!chain_bad && !boundary_bad && S.index_sm3 && *S.index_sm3 && access(idx_path, R_OK) == 0) {
        char idx_sm3_now[65];
        if (sm3_file_hex(idx_path, idx_sm3_now) == 0) {
            if (strcmp(idx_sm3_now, S.index_sm3) == 0) idx_ok = 1;
            // else { fprintf(stderr, "[ALERT] index_sm3 mismatch: %s\n", idx_path); trigger_alarm("Index file hash mismatch: index may be tampered!"); }
            else { fprintf(stderr, _("[ALERT] index_sm3 mismatch: %s\n"), idx_path); trigger_alarm(_("Index file hash mismatch: index may be tampered!")); } //11-3
        }
    } else if (!chain_bad && !boundary_bad && access(idx_path, R_OK) != 0) {
        // fprintf(stderr, "[WARN] missing idx: %s (仅链式校验已通过)\n", idx_path);
        fprintf(stderr, _("[WARN] missing idx: %s (chain check only passed)\n"), idx_path); //11-3
    }

    if (!chain_bad && !boundary_bad && idx_ok) {
        unsigned long long idx_last_i = 0; char idx_last_chash[65] = {0};
        if (read_idx_tail(idx_path, &idx_last_i, idx_last_chash) == 0) {
            if (idx_last_i == strtoull(S.records, NULL, 10) &&
                strcmp(idx_last_chash, S.chain) == 0) {
                idx_tail_ok = 1;
            } else {
                // fprintf(stderr,"[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%s; idx_chash=%s vs seal_chain=%s\n",
                //11-3
                fprintf(stderr,_("[ALERT] idx tail vs seal mismatch: idx_i=%llu seal_records=%s; idx_chash=%s vs seal_chain=%s\n"),
                        idx_last_i, S.records, idx_last_chash, S.chain);
                // trigger_alarm("Seal/Index mismatch: records or chain differ!");
                trigger_alarm(_("Seal/Index mismatch: records or chain differ!")); //11-3
            }
        }
    }
    if (!chain_bad && !boundary_bad && idx_ok && idx_tail_ok) {
        BadList bl; badlist_init(&bl);
        unsigned long long scanned2 = 0;
        unsigned long long bad = compare_with_index_limit(log_path, idx_path, &bl, strtoull(S.records, NULL, 10), &scanned2);
        if (bad == (unsigned long long)-1) {
            // fprintf(stderr, "[WARN] cannot compare audit.log vs idx (skip)\n");
            fprintf(stderr, _("[WARN] cannot compare audit.log vs idx (skip)\n")); //11-3
        } else if (bad == strtoull(S.records, NULL, 10) && strtoull(S.records, NULL, 10) > 0) {
            // fprintf(stderr, "[WARN] All %s lines differ (RAW/ENRICHED mismatch suspected). Set auditd log_format=RAW.\n", S.records);
            fprintf(stderr, _("[WARN] All %s lines differ (RAW/ENRICHED mismatch suspected). Set auditd log_format=RAW.\n"), S.records); //11-3
         } else if (bad > 0) {
        } else if (bad > 0) {
            // trigger_alarm("Found mismatched lines against index (possible format change)");
            trigger_alarm(_("Found mismatched lines against index (possible format change)")); //11-3
        }
        badlist_free(&bl);
    }

    /* FIX: 只有“既没有链失配，也没有边界问题”才打印 OK；否则不打印 OK（上面已有 FAIL/ALARM 信息） */
    // if (!chain_bad && !boundary_bad) fprintf(stderr, "[OK] verify success\n\n");
    if (!chain_bad && !boundary_bad) fprintf(stderr, _("[OK] verify success\n\n")); //11-3

    EVP_PKEY_free(pub); X509_free(cert); free_seal(&S); free(idx_path);
    /* FIX: 返回码也要把 boundary_bad 计入 */
    return (chain_bad || boundary_bad) ? 1 : 0;
}




/* 判断是否以给定后缀结尾 */
static int has_suffix(const char *s, const char *suf) {
    size_t n = strlen(s), m = strlen(suf);
    return n >= m && strcmp(s + (n - m), suf) == 0;
}

/* 从文件名中提取时间戳：
   1) 新格式：...".tampered-"<ts>"-..."
   2) 兼容旧格式："...-"<ts>[".seal"]（取最后一个 '-' 后的数字）
   成功返回 1 并写入 *out_ts，失败返回 0 */
static int parse_ts_from_name(const char *name, long long *out_ts) {
    const char *p = strstr(name, ".tampered-");
    if (p) {
        p += strlen(".tampered-");
        const char *q = p;
        while (*q >= '0' && *q <= '9') q++;
        if (q > p) {
            errno = 0;
            long long v = strtoll(p, NULL, 10);
            if (errno == 0) { *out_ts = v; return 1; }
        }
    }
    /* 兼容老命名：从结尾往前找最后一个 '-'，尝试解析后续数字 */
    const char *dot = strrchr(name, '.');                /* 可能是 ".seal" */
    const char *end = dot ? dot : name + strlen(name);   /* 解析到 '.' 之前 */
    const char *dash = NULL;
    for (const char *r = end; r > name; --r) {
        if (r[-1] == '-') { dash = r - 1; break; }
    }
    if (dash) {
        const char *q = dash + 1;
        if (*q >= '0' && *q <= '9') {
            errno = 0;
            char *ep = NULL;
            long long v = strtoll(q, &ep, 10);
            if (errno == 0 && ep > q) { *out_ts = v; return 1; }
        }
    }
    return 0;
}

typedef struct {
    char path[PATH_MAX];
    long long sort_ts; /* 抽取到的时间戳；抽不到就用 mtime */
} SealItem;

int cmp_item(const void *a, const void *b) {
    const SealItem *x = (const SealItem *)a;
    const SealItem *y = (const SealItem *)b;
    // 时间戳升序（旧日志→新日志），时间相同则按路径排序
    if (x->sort_ts < y->sort_ts) return -1;
    if (x->sort_ts > y->sort_ts) return 1;
    return strcmp(x->path, y->path); 
}

int main(int argc, char **argv)
{
    // if (argc != 2) {
    //     // fprintf(stderr, "Usage:\n  %s /var/log/audit/audit.log.seal\n  %s /var/log/audit/   (目录，批量验证 *.seal)\n", argv[0], argv[0]);
    //     fprintf(stderr, _("Usage:\n  %s /var/log/audit/audit.log.seal\n  %s /var/log/audit/   (directory: verify all *.seal)\n"), argv[0], argv[0]); //11-3
    //     return 1;
    // }

    // i18n_init();
    // /* i18n init: allow AUDIT_I18N_LANG to override locale */
    // const char *force_lang = getenv("AUDIT_I18N_LANG");
    // if (force_lang && *force_lang) {
    //     setlocale(LC_ALL, force_lang);
    // } else {
    //     setlocale(LC_ALL, "");
    // }


    /* -------- 解析 --lang=xx 参数；默认英文 -------- */
   const char *opt_lang = NULL;
   const char *path_arg = NULL;
   for (int i = 1; i < argc; ++i) {
       if (strncmp(argv[i], "--lang=", 7) == 0) {
           opt_lang = argv[i] + 7;
       } else if (strcmp(argv[i], "--lang") == 0 && i + 1 < argc) {
           opt_lang = argv[++i];
       } else if (!path_arg) {
           path_arg = argv[i];
       } else {
           /* 多余参数，直接报用法 */
           path_arg = NULL; break;
       }
   }
   if (!path_arg) {
       fprintf(stderr,
               "Usage:\n"
               "  %s [--lang=zh_CN|en|C] /var/log/audit/audit.log.seal\n"
               "  %s [--lang=zh_CN|en|C] /var/log/audit/   (directory: verify all *.seal)\n",
               argv[0], argv[0]);
       return 1;
   }
   /* 默认英文；指定 zh_CN 时切中文。
      注意：GNU gettext 下，LANGUAGE 在 LC_ALL=“非 C ”时才生效。
      这里：
        - 英文：强制 LC_ALL=C（即 msgid 原文）
        - 其它：尝试 setlocale 到该语言；再设置 LANGUAGE 做优先级覆盖 */
   if (!opt_lang || !*opt_lang || strcasecmp(opt_lang, "en") == 0 || strcasecmp(opt_lang, "C") == 0) {
       setenv("LANGUAGE", "C", 1);
       setlocale(LC_ALL, "C");
   } else {
       /* 允许传 zh_CN 或 zh_CN.UTF-8 都行；setlocale 失败则回退系统默认 */
       if (!setlocale(LC_ALL, opt_lang)) {
           setlocale(LC_ALL, "");
       }
       setenv("LANGUAGE", opt_lang, 1);
   }
   i18n_init();


    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();
    // if (gm_crypto_init() != 0) diee("gm init failed");
    // if (!EVP_get_digestbyname("sm3")) diee("[ERROR] 无 sm3");
    if (gm_crypto_init() != 0) diee(_("gm init failed"));
    if (!EVP_get_digestbyname("sm3")) diee(_("[ERROR] sm3 not available"));
    

    int rc = 0;

    if (is_directory(path_arg)) {
        DIR *d = opendir(path_arg);
        // if (!d) diee("cannot open directory");
        if (!d) diee(_("cannot open directory"));//11-3

        SealItem *items = NULL;
        size_t count = 0, cap = 0;

        struct dirent *de;
        while ((de = readdir(d))) {
            if (!has_suffix(de->d_name, ".seal")) continue;

                char path[PATH_MAX];
                snprintf(path, sizeof(path), "%s/%s", path_arg, de->d_name);

                /* 抽取排序用时间戳；抽不到则回退到文件 mtime */
                long long ts = 0;
                if (!parse_ts_from_name(de->d_name, &ts)) {
                    struct stat st;
                    if (stat(path, &st) == 0) ts = (long long)st.st_mtime;
                    else ts = 0;
                }

                if (count == cap) {
                    size_t ncap = cap ? cap * 2 : 64;
                    void *p = realloc(items, ncap * sizeof(SealItem));
                    if (!p) { closedir(d); free(items); diee("oom"); }
                    items = (SealItem *)p; cap = ncap;
                }
                strncpy(items[count].path, path, sizeof(items[count].path) - 1);
                items[count].path[sizeof(items[count].path) - 1] = 0;
                items[count].sort_ts = ts;
                count++;
            }
            closedir(d);
            qsort(items, count, sizeof(SealItem), cmp_item);

            for (size_t i = 0; i < count; i++) {
                int one = verify_one(items[i].path);
                if (one != 0) rc = 1;
            }
            free(items);
        } else {
            rc = verify_one(path_arg);
            
    }


    gm_crypto_cleanup();
    return rc;
}
